#ifndef _H_C_PROMISE_H_
#define _H_C_PROMISE_H_

#include "../typedefine.h"
#include <list>
#include <memory>
#include <iostream>
#include <stdexcept>
#include "CCurThreadSyncContext.h"
#include "EPromiseStatus.h"
#include "PromiseDelegate.h"
#include "../threadpool.h"
#include "ISynchronizationContext.h"

namespace TB
{
	template<typename T> struct SPromiseProcessor;

	template<typename T>
	struct SPromise
	{
	private:
		class CInfo
		{
			NON_COPYABLE(CInfo)
		public:
			CInfo()
			{
			}
			CInfo(const T& oRet) :
				m_eStatus(EPromiseStatus::Fulfilled), m_oRet(oRet)
			{
			}
			CInfo(const std::shared_ptr<std::exception>& poException):
				m_eStatus(EPromiseStatus::Rejected), m_poException(poException)
			{
			}
			inline EPromiseStatus GetStatus() const { return m_eStatus; }
			inline const T& GetRet() const { return m_oRet; }
			inline T& GetRet() { return m_oRet; }
			inline const std::shared_ptr<std::exception>& GetException() const { return m_poException; }
			inline std::shared_ptr<std::exception>& GetException() { return m_poException; }

			bool Resolve(const T& oRet)
			{
				if (m_eStatus != EPromiseStatus::Pending)
					return false;
				std::vector<std::function<void()>> oNextTasks;
				{
					CSpinLock oLock(m_oSpinLockFlag);
					if (m_eStatus != EPromiseStatus::Pending)
						return false;
					m_oRet = oRet;
					m_eStatus = EPromiseStatus::Fulfilled;
					oNextTasks.swap(m_oNextTasks);
				}
				for (UINT32 i = 0; i != oNextTasks.size(); ++i)
				{
					oNextTasks[i]();
				}
				return true;
			}
			bool Reject(const std::shared_ptr<std::exception>& poException)
			{
				if (m_eStatus != EPromiseStatus::Pending)
					return false;
				std::vector<std::function<void()>> oNextTasks;
				{
					CSpinLock oLock(m_oSpinLockFlag);
					if (m_eStatus != EPromiseStatus::Pending)
						return false;
					m_poException = poException;
					m_eStatus = EPromiseStatus::Rejected;
					oNextTasks.swap(m_oNextTasks);
				}
				for (UINT32 i = 0; i != oNextTasks.size(); ++i)
				{
					oNextTasks[i]();
				}
				return true;
			}
			template<typename U>
			static void ResolveOrRejectFromPromise(std::shared_ptr<CInfo>& poRetInfo, const SPromise<U>& roPromise)
			{
				if (roPromise.m_poInfo == nullptr)
					poRetInfo->Resolve(roPromise.m_oData);
				else
				{
					auto poInfo1 = roPromise.m_poInfo;
					roPromise.m_poInfo->Then([poInfo1, poRetInfo]()
						{
							EPromiseStatus eStatus = poInfo1->GetPromiseStatus();
							if (eStatus == EPromiseStatus::Fulfilled)
								poRetInfo->Resolve(poInfo1->GetRet());
							else
								poRetInfo->Reject(poInfo1->GetException());
						});
				}
			}

			void Then(const std::function<void()>& rcbTask)
			{
				if (m_eStatus == EPromiseStatus::Pending)
				{
					CSpinLock oLock(m_oSpinLockFlag);
					if (m_eStatus == EPromiseStatus::Pending)
					{
						m_oNextTasks.push_back(rcbTask);
						return;
					}
				}
				rcbTask();
			}
		private:
			EPromiseStatus m_eStatus = EPromiseStatus::Pending;
			T m_oRet;
			std::shared_ptr<std::exception> m_poException;
			std::vector<std::function<void()>> m_oNextTasks;
			CSpinLockFlag m_oSpinLockFlag;
		};

	private:
		explicit SPromise(bool bIsNull, int) 
		{
			if (!bIsNull)
				m_poInfo = std::make_shared<CInfo>();
		}
		explicit SPromise(const T& oData)
		{
			m_oData = oData;
		}
		explicit SPromise(const std::shared_ptr<std::exception>& poError)
		{
			m_poInfo = std::make_shared<CInfo>(poError);
		}

	public:
		inline EPromiseStatus GetStatus() const
		{
			return m_poInfo == nullptr ? EPromiseStatus::Fulfilled : m_poInfo->GetStatus();
		}
		
		template<typename RET>
		SPromise<RET> Then(const DPromiseResolver<RET, T>& rcbOnResolve) const
		{
			if (!rcbOnResolve)
				throw std::runtime_error("null callback");
			if (m_poInfo == nullptr)
				return rcbOnResolve(m_oData);
			if (m_poInfo->GetStatus() == EPromiseStatus::Fulfilled)
				return rcbOnResolve(m_poInfo->GetRet());
			if (m_poInfo->GetStatus() == EPromiseStatus::Rejected)
				return *this;
			std::shared_ptr<CInfo> poRetInfo = std::make_shared<CInfo>();
			CCurThreadSyncContext& roCurThreadSyncContext = CCurThreadSyncContext::GetCurrent();
			auto poInfo = m_poInfo;
			m_poInfo->Then([poInfo, poRetInfo, &roCurThreadSyncContext, rcbOnResolve]()
			{
				if (poInfo->GetPromiseStatus() == EPromiseStatus::Rejected)
				{
					poRetInfo->Reject(poInfo->GetException());
					return;
				}
				auto poContext = roCurThreadSyncContext.GetSynchronizationContext();
				if (poContext == nullptr)
				{
					SPromise<RET> oPromise = rcbOnResolve(poInfo->GetRet());
					CInfo::ResolveOrRejectFromPromise(poRetInfo, oPromise);
					return;
				}
				poContext->Post([poInfo, poRetInfo, rcbOnResolve]()
				{
					SPromise<RET> oPromise = rcbOnResolve(poInfo->GetRet());
					CInfo::ResolveOrRejectFromPromise(poRetInfo, oPromise);
				});
			});
			SPromise<RET> oRet;
			oRet.m_poInfo = poRetInfo;
			return oRet;
		}

		template<typename RET>
		SPromise<RET> Catch(const DPromiseRejecter<RET>& rcbOnReject) const
		{
			if (!rcbOnReject)
				throw std::runtime_error("null callback");
			if (m_poInfo == nullptr)
				return *this;
			if (m_poInfo->GetStatus() == EPromiseStatus::Fulfilled)
				return *this;
			if (m_poInfo->GetStatus() == EPromiseStatus::Rejected)
			{
				return rcbOnReject(m_poInfo->GetException());
			}
			std::shared_ptr<CInfo> poRetInfo = std::make_shared<CInfo>();
			CCurThreadSyncContext& roCurThreadSyncContext = CCurThreadSyncContext::GetCurrent();
			auto poInfo = m_poInfo;
			m_poInfo->Then([poInfo, poRetInfo, &roCurThreadSyncContext, rcbOnReject]()
			{
				if (poInfo->GetPromiseStatus() == EPromiseStatus::Fulfilled)
				{
					poRetInfo->Resolve(poInfo->GetRet());
					return;
				}
				auto poContext = roCurThreadSyncContext.GetSynchronizationContext();
				if (poContext == nullptr)
				{
					SPromise<RET> oPromise = rcbOnReject(poInfo->GetException());
					CInfo::ResolveOrRejectFromPromise(poRetInfo, oPromise);
					return;
				}
				poContext->Post([poInfo, poRetInfo, rcbOnReject]()
				{
					SPromise<RET> oPromise = rcbOnReject(poInfo->GetException());
					CInfo::ResolveOrRejectFromPromise(poRetInfo, oPromise);
				});
			});
			SPromise<RET> oRet;
			oRet.m_poInfo = poRetInfo;
			return oRet;
		}

	private:
		template<typename U> friend struct SPromise;
		friend struct SPromiseProcessor<T>;
		friend class CPromiseHelper;

		T m_oData;
		std::shared_ptr<CInfo> m_poInfo;
	};

	using SVoidPromise = SPromise<bool>;
}

#endif
